/**
 *  This file is part of OhSnap.
 *
 * 	Copyright (c) 2009 Gabriel Mendon�a and Marcelo Machado
 * 
 *  OhSnap is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OhSnap is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OhSnap.  If not, see <http://www.gnu.org/licenses/>.
 *  
 */
package br.mm.cg.particles;

import java.util.LinkedList;
import java.util.List;

import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PVector;

/**
 * @author machado
 * 
 */
public class Generator implements Comparable<Generator> {

	private static final float DEFAULT_RADIUS = 100.0f;

	private static final float ALPHA = 30.0f;

	protected PApplet parent;
	protected PVector position;
	protected PVector destination;
	protected PVector multiplier;
	protected PVector rotation;
	protected PVector max;
	protected PVector min;
	protected int capacity;
	protected float speed;
	protected float radius;
	protected float[] color;
	protected List<Particle> particles;

	public boolean isDead = false;
	public PVector transformedCenter;

	/**
	 * Constructor.
	 * 
	 * @param pApplet
	 *            the PApplet where this Generator will be
	 * @param position
	 *            a PVector with the initial position of this generator
	 * @param capacity
	 *            how many particles this generator can create
	 * @param max
	 *            the max bound of his parent
	 * @param min
	 *            the min bound of his parent
	 * @param particles
	 *            a particles linked list to set this generator with
	 */
	public Generator(PApplet pApplet, PVector position, int capacity,
			PVector max, PVector min, List<Particle> particles) {

		parent = pApplet;
		this.position = position;
		this.destination = position.get();
		this.capacity = capacity;
		this.speed = 0.1f;
		this.max = max;
		this.min = min;
		this.radius = DEFAULT_RADIUS;
		this.multiplier = new PVector(1.0f, 1.0f, 1.0f);

		this.color = new float[] { parent.random(256), parent.random(256),
				parent.random(256) };

		this.rotation = new PVector(parent.random(-1.0f, 1.0f), parent.random(
				-1.0f, 1.0f), parent.random(-1.0f, 1.0f));
		this.rotation.normalize();

		if (particles != null) {
			this.particles = particles;
		} else {
			this.particles = new LinkedList<Particle>();
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param pApplet
	 *            the PApplet where this Generator will be
	 * @param position
	 *            a PVector with the initial position of this generator
	 * @param capacity
	 *            how many particles this generator can create
	 * @param max
	 *            the max bound of his parent
	 * @param min
	 *            the min bound of his parent
	 */
	public Generator(PApplet pApplet, PVector position, int capacity,
			PVector max, PVector min) {
		this(pApplet, position, capacity, max, min, null);
	}

	/**
	 * Draws all particles of this generator.
	 * @param pGraphics 
	 */
	public void draw(PGraphics pGraphics) {
		pGraphics.pushMatrix();
		pGraphics.translate(position.x, position.y, position.z);

		float angle = (parent.frameCount / 100.0f) % PApplet.TWO_PI;
		pGraphics.rotate(angle, rotation.x, rotation.y, rotation.z);

		for (Particle p : particles) {
			if (p.position.mag() >= radius) {
				p.velocity.mult(-1.0f);
			}

			PVector displacement = PVector.mult(p.velocity, p.speed);
			p.position.add(displacement);
			p.draw(pGraphics);
		}

		pGraphics.fill(color[0], color[1], color[2], ALPHA);
		pGraphics.sphere(this.radius + 10.0f);

		float x = pGraphics.modelX(0.0f, 0.0f, 0.0f);
		float y = pGraphics.modelY(0.0f, 0.0f, 0.0f);
		float z = pGraphics.modelZ(0.0f, 0.0f, 0.0f);
		this.transformedCenter = new PVector(x, y, z);

		pGraphics.popMatrix();
	}

	/**
	 * Updates the generator position, giving its current position, destination
	 * and speed.
	 */
	public void move() {
		if (destination != null) {
			PVector movement = PVector.sub(destination, position);
			movement.mult(speed);
			position.add(movement);

			float[] positionArray = { position.x, position.y, position.z };
			float[] destinationArray = { destination.x, destination.y,
					destination.z };
			float[] multiplierArray = { multiplier.x, multiplier.y,
					multiplier.z };
			float[] maxArray = { max.x, max.y, max.z };
			float[] minArray = { min.x, min.y, min.z };

			for (int i = 0; i < 3; i++) {
				if (positionArray[i] >= maxArray[i]) {
					destinationArray[i] = 2 * maxArray[i] - positionArray[i];
					positionArray[i] = maxArray[i];
					multiplierArray[i] += -2 * multiplierArray[i];
				}
				if (positionArray[i] <= minArray[i]) {
					destinationArray[i] = 2 * minArray[i] - positionArray[i];
					positionArray[i] = minArray[i];
					multiplierArray[i] += -2 * multiplierArray[i];
				}
			}

			position.set(positionArray);
			destination.set(destinationArray);
			multiplier.set(multiplierArray);
		}
	}

	/**
	 * Adds a Particle to the Generator
	 */
	public void addParticle() {
		if (particles.size() < capacity) {
			float[] particleColor = new float[3];
			float noise = parent.random(0.5f, 1.0f);

			for (int i = 0; i < 3; i++) {
				particleColor[i] = this.color[i] * noise;
			}

			Particle p = new Particle(parent, new PVector(),
					getRandomMovement(), particleColor);
			p.speed = parent.random(1.0f, 2.5f);
			particles.add(p);
		}
	}

	/**
	 * Generate a random movement vector for a particle
	 * 
	 * @return a PVector with a random movement vector
	 */
	public PVector getRandomMovement() {
		PVector movement = new PVector(parent.random(-1.0f, 1.0f), parent
				.random(-1.0f, 1.0f), parent.random(-1.0f, 1.0f));

		movement.normalize();
		return movement;
	}

	/**
	 * @return the position
	 */
	public PVector getPosition() {
		return position;
	}

	/**
	 * @param position
	 *            the position to set
	 */
	public void setPosition(PVector position) {
		this.position = position;
	}

	/**
	 * @return the destination
	 */
	public PVector getDestination() {
		return destination;
	}

	/**
	 * @param destination
	 *            the destination to set
	 */
	public void setDestination(PVector destination) {
		this.destination = destination;
	}

	/**
	 * @return the capacity
	 */
	public int getCapacity() {
		return capacity;
	}

	/**
	 * @param capacity
	 *            the capacity to set
	 */
	public void setCapacity(int capacity) {
		this.capacity = capacity;
	}

	/**
	 * @return the speed
	 */
	public float getSpeed() {
		return speed;
	}

	/**
	 * @param speed
	 *            the speed to set
	 */
	public void setSpeed(float speed) {
		this.speed = speed;
	}

	/**
	 * @return the color
	 */
	public float[] getColor() {
		return color;
	}

	/**
	 * @return the color in Processing's format
	 */
	public int getP5Color() {
		return parent.color(color[0], color[1], color[2]);
	}

	/**
	 * @param color
	 *            the color to set
	 */
	public void setColor(float[] color) {
		this.color = color;
	}

	/**
	 * @param color
	 *            the color to set
	 */
	public void setColor(int color) {
		this.color = new float[] { parent.red(color), parent.green(color),
				parent.blue(color) };
	}

	/**
	 * @return the particles
	 */
	public List<Particle> getParticles() {
		return particles;
	}

	/**
	 * @param particles
	 *            the particles to set
	 */
	public void setParticles(List<Particle> particles) {
		this.particles = particles;
	}

	/**
	 * @return the multiplier
	 */
	public PVector getMultiplier() {
		return multiplier;
	}

	/**
	 * @param multiplier
	 *            the multiplier to set
	 */
	public void setMultiplier(PVector multiplier) {
		this.multiplier = multiplier;
	}

	/**
	 * @return the rotation
	 */
	public PVector getRotation() {
		return rotation;
	}

	/**
	 * @param rotation
	 *            the rotation to set
	 */
	public void setRotation(PVector rotation) {
		this.rotation = rotation;
	}

	/**
	 * @return the radius
	 */
	public float getRadius() {
		return radius;
	}

	/**
	 * @param radius
	 *            the radius to set
	 */
	public void setRadius(float radius) {
		this.radius = radius;
	}

	public boolean isFull() {
		return (particles.size() == capacity);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	/**
	 * Compares the Generators based in z coordinates.
	 */
	@Override
	public int compareTo(Generator o) {
		return (int) (this.position.z - o.position.z);
	}
}
